/**
 * 在内置工具类型中，对 结构工具类型（回顾：16_工具类型_3、结构工具类型.ts） 的进阶主要分为：
 * 1、基于键值类型的 Pick 与 Omit
 * 2、子结构的互斥处理
*/

/**
 * 1、基于键值类型的 Pick 与 Omit，可以称之为 PickByValueType
 * 它的实现方式：
 * 其实还是类似部分属性修饰中那样，将对象拆分为两个部分，处理完毕再组装。
 * 只不过，现在 无法预先确定 要拆分的属性了，
 * 而是需要 基于期望的类型 去拿到 所有此类型的属性名，
*/
//测试数据
type useObject = { foo: string; bar: number; baz: boolean };
//1、通过 ExpectedPropKeys，能够获取到 所有符合预期类型 的 属性名。
//为了避免可选属性对条件类型语句造成干扰，这里我们使用 -? 移除了所有可选标记。
type ExpectedPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? Key : never;
}[keyof T];
//TestOne相应类型的属性名："foo" | "bar"
type TestOne = ExpectedPropKeys<useObject, string | number>

//2、通过 ExpectedPropKeys 拿到对应类型的属性名，再把这些属性交给 Pick，就可以得到由这些属性组成的子结构
type PickByValueType<T extends object, ValueType> = Pick<
  T,
  ExpectedPropKeys<T, ValueType>
>;
type Test = PickByValueType<useObject, string | number>
//示例使用
let testUse: Test = {
  foo: "hanxuming",
  bar: 1
}

//OmitByValueType 与 实现 PickByValueType 类似
type FilteredPropKeys<T extends object, ValueType> = {
  [Key in keyof T]-?: T[Key] extends ValueType ? never : Key;
}[keyof T];

type OmitByValueType<T extends object, ValueType> = Pick<
  T,
  FilteredPropKeys<T, ValueType>
>;

/**
 * 2、基于结构的互斥工具类型
 * 
 * 互斥工具类型在很多实战场景下都有重要意义，它在联合类型的基础上添加了属性间的互斥逻辑
 * 
 * 假设：
 * 有一个用于描述用户信息的对象结构，
 * 除了共有的一些基础结构以外，还有，VIP 用户和普通用户、游客这三种类型
 * 这三种类型用户各自拥有一些独特的字段，
 * 如 vipExpires 代表 VIP 过期时间，仅属于 VIP 用户，
 * promotionUsed 代表已领取过体验券，属于普通用户，
 * 而 refererType 代表跳转来源，属于游客
*/

interface VIP {
  vipExpires: number;
}

interface CommonUser {
  promotionUsed: boolean;
}

//Without 它其实就是将声明了一个不变的原属性+为 never 的其他属性的接口
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

type XORUser = XOR<VIP, CommonUser>;
// 报错，不允许同时拥有
let testStruct: XORUser = {
  vipExpires: 1,
  promotionUsed: false
}


//加上 游客类型 实现三个互斥属性
interface Visitor {
  refererType: string;
}
// 联合类型会自动合并重复的部分
type XORAll = XOR<VIP, XOR<CommonUser, Visitor>>;
// 报错，不允许同时拥有
let testXORALL: XORAll = {
  refererType: "hanxuming",
  promotionUsed: false
}

//还可以使用互斥类型实现绑定效果，即要么同时拥有 A、B 属性，要么一个属性都没有
type XORStruct = XOR<
  {},
  {
    foo: string;
    bar: number;
  }
>;

//export {}：解决“无法重新声明块范围变量”错误提示问题
export { }